pub mod aat;
pub mod cff;
pub mod ggg;
pub mod var;

mod basic;
mod names;
mod offset;
mod parser;
pub use basic::*;
pub use names::*;
pub use offset::*;
pub use parser::*;

mod tables;
pub use tables::*;

pub fn guess_format(data: &[u8]) -> Option<(Magic, u32)> {
	let mut s = Stream::new(data);
	let magic: Magic = s.read()?;
	Some(match magic {
		Magic::TrueType | Magic::OpenType => (magic, 1),
		Magic::Collection => {
			let _version: u32 = s.read()?;
			let num_faces: u32 = s.read()?;
			(magic, num_faces)
		}
	})
}
pub fn parse_face(data: &'_ [u8], index: u32) -> Option<Face<'_>> {
	let mut s = Stream::new(data);
	let magic = s.read::<Magic>()?;
	match magic {
		Magic::Collection => {
			let _version = s.read::<u32>()?;
			let offset = s.parse_array32::<u32>()?.get(index)?;
			s.seek(offset as usize);
			let magic = s.read::<Magic>()?;
			match magic {
				Magic::OpenType | Magic::TrueType => Face::parse(&mut s),
				_ => return None,
			}
		}
		Magic::TrueType | Magic::OpenType => Face::parse(&mut s),
	}
}

#[derive(Debug, Clone, Default)]
pub struct Face<'a> {
	pub head: Option<head::Table>,
	pub maxp: Option<maxp::Table>,
	pub fmtx: Option<fmtx::Table<'a>>,

	pub hhea: Option<hhea::Table>,
	pub hmtx: Option<hmtx::Table<'a>>,
	pub hdmx: Option<hdmx::Table<'a>>,

	pub vhea: Option<vhea::Table>,
	pub vmtx: Option<hmtx::Table<'a>>,
	pub vdmx: Option<vdmx::Table<'a>>,

	pub cmap: Option<cmap::Table<'a>>,
	// Font Infomation
	pub name: Option<name::Table<'a>>,
	pub os_2: Option<os_2::Table>,
	pub post: Option<post::Table<'a>>,
	pub meta: Option<meta::Table<'a>>,
	pub stat: Option<stat::Table<'a>>,
	// Glyphs TrueType Outlines
	pub glyf: Option<glyf::Table<'a>>,
	pub loca: Option<loca::Table<'a>>,
	// Glyphs CFF Outlines
	pub cff1: Option<cff1::Table<'a>>,
	pub cff2: Option<cff2::Table<'a>>,
	pub vorg: Option<vorg::Table<'a>>,
	// Glyphs SVG Outlines
	pub svg_: Option<svg_::Table<'a>>,
	// Glyphs Bitmap
	pub bdat: Option<cbdt::Table<'a>>,
	pub bloc: Option<cblc::Table<'a>>,
	pub bhed: Option<head::Table>,
	pub colr: Option<colr::Table<'a>>,
	pub cpal: Option<cpal::Table<'a>>,
	pub cbdt: Option<cbdt::Table<'a>>,
	pub cblc: Option<cblc::Table<'a>>,
	pub ebdt: Option<cbdt::Table<'a>>,
	pub eblc: Option<cblc::Table<'a>>,
	pub ebsc: Option<ebsc::Table<'a>>,
	pub sbix: Option<sbix::Table<'a>>,
	// Opentype Layout GGG
	pub base: Option<base::Table<'a>>,
	pub gdef: Option<gdef::Table<'a>>,
	pub gsub: Option<gsub::Table<'a>>,
	pub gpos: Option<gpos::Table<'a>>,
	pub jstf: Option<jstf::Table<'a>>,
	pub math: Option<math::Table<'a>>,
	// Apple Advanced Typography AAT
	pub acnt: Option<acnt::Table<'a>>,
	pub ankr: Option<ankr::Table<'a>>,
	pub bsln: Option<bsln::Table<'a>>,
	pub fdsc: Option<fdsc::Table<'a>>,
	pub feat: Option<feat::Table<'a>>,
	pub just: Option<just::Table<'a>>,
	pub kern: Option<kern::Table<'a>>,
	pub kerx: Option<kerx::Table<'a>>,
	pub lcar: Option<lcar::Table<'a>>,
	pub ltag: Option<ltag::Table<'a>>,
	pub morx: Option<morx::Table<'a>>,
	pub opbd: Option<opbd::Table<'a>>,
	pub prop: Option<prop::Table<'a>>,
	pub trak: Option<trak::Table<'a>>,
	pub zapt: Option<zapf::Table<'a>>,
	// OpenType Font Variations
	pub avar: Option<avar::Table<'a>>,
	pub cvar: Option<cvar::Table<'a>>,
	pub fvar: Option<fvar::Table<'a>>,
	pub gvar: Option<gvar::Table<'a>>,
	pub hvar: Option<hvar::Table<'a>>,
	pub mvar: Option<mvar::Table<'a>>,
	pub vvar: Option<vvar::Table<'a>>,
	// Other OpenType table
	pub ltsh: Option<ltsh::Table<'a>>,
	pub merg: Option<merg::Table<'a>>,
	pub pclt: Option<pclt::Table<'a>>,
	// Other Truetype table
	pub fond: Option<fond::Table<'a>>,
	pub gcid: Option<gcid::Table<'a>>,
	pub xref: Option<xref::Table<'a>>,
	// Other
	pub fftm: Option<fftm::Table>,
}
impl<'a> Face<'a> {
	pub fn parse(s: &mut Stream<'a>) -> Option<Self> {
		let mut this: Self = Default::default();

		let num_tables = s.read::<u16>()?;
		let _search_range = s.read::<u16>()?;
		let _entry_selector = s.read::<u16>()?;
		let _range_shift = s.read::<u16>()?;
		let records = s.read_array16::<TableRecord>(num_tables)?;

		let tables = RawTables::parse(s.data(), records);

		this.head = head::Table::parse(tables.head?);
		this.maxp = maxp::Table::parse(tables.maxp?);

		let head = this.head.as_ref();
		let maxp = this.maxp.as_ref();
		let num_glyphs = maxp.map(|v| v.num_glyphs);

		this.hhea = hhea::Table::parse(tables.hhea?);
		this.hmtx = hmtx::Table::parse(this.hhea.as_ref()?.num_metrics, num_glyphs?, tables.hmtx?);
		this.hdmx = tables.hdmx.and_then(|data| hdmx::Table::parse(num_glyphs?, data));

		this.vhea = tables.vhea.and_then(vhea::Table::parse);
		this.vmtx = tables.vmtx.and_then(|data| {
			hmtx::Table::parse(this.vhea.as_ref()?.num_metrics, num_glyphs?, data) //
		});
		this.vdmx = tables.vdmx.and_then(vdmx::Table::parse);

		this.cmap = cmap::Table::parse(tables.cmap?);

		this.name = name::Table::parse(tables.name?);
		this.os_2 = tables.os_2.and_then(os_2::Table::parse);
		this.post = tables.post.and_then(post::Table::parse);
		this.meta = tables.meta.and_then(meta::Table::parse);
		this.stat = tables.stat.and_then(stat::Table::parse);
		// Glyphs TrueType Outlines
		this.loca = tables.loca.and_then(|data| {
			loca::Table::parse(num_glyphs?.try_into().ok()?, head?.loca_format, data) //
		});
		this.glyf = tables.glyf.and_then(|data| glyf::Table::parse(this.loca.clone()?, data));
		// Glyphs CFF Outlines
		this.cff1 = tables.cff1.and_then(cff1::Table::parse);
		this.cff2 = tables.cff2.and_then(cff2::Table::parse);
		this.vorg = tables.vorg.and_then(vorg::Table::parse);
		// Glyphs SVG Outlines
		this.svg_ = tables.svg_.and_then(svg_::Table::parse);
		// Glyphs Bitmap
		this.bhed = tables.bhed.and_then(head::Table::parse);
		this.bloc = tables.bloc.and_then(cblc::Table::parse);
		this.bdat = tables.bdat.and_then(|data| cbdt::Table::parse(this.bloc.clone()?, data));
		this.cpal = tables.cpal.and_then(cpal::Table::parse);
		this.colr = tables.colr.and_then(|data| colr::Table::parse(this.cpal.clone()?, data));
		this.cblc = tables.cblc.and_then(cblc::Table::parse);
		this.cbdt = tables.cbdt.and_then(|data| cbdt::Table::parse(this.cblc.clone()?, data));
		this.eblc = tables.eblc.and_then(cblc::Table::parse);
		this.ebdt = tables.ebdt.and_then(|data| cbdt::Table::parse(this.cblc.clone()?, data));
		this.sbix = tables.sbix.and_then(|data| sbix::Table::parse(num_glyphs?.try_into().ok()?, data));
		// Opentype Layout GGG
		this.base = tables.base.and_then(base::Table::parse);
		this.gdef = tables.gdef.and_then(gdef::Table::parse);
		this.gsub = tables.gsub.and_then(gsub::Table::parse);
		this.gpos = tables.gpos.and_then(gpos::Table::parse);
		this.jstf = tables.jstf.and_then(jstf::Table::parse);
		this.math = tables.math.and_then(math::Table::parse);
		// Apple Advanced Typography AAT
		this.ankr = tables.ankr.and_then(|data| ankr::Table::parse(num_glyphs?, data));
		this.bsln = tables.bsln.and_then(|data| bsln::Table::parse(num_glyphs?, data));
		this.fdsc = tables.fdsc.and_then(fdsc::Table::parse);
		this.feat = tables.feat.and_then(feat::Table::parse);
		this.just = tables.just.and_then(|data| just::Table::parse(num_glyphs?, data));
		this.kern = tables.kern.and_then(kern::Table::parse);
		this.kerx = tables.kerx.and_then(|data| kerx::Table::parse(num_glyphs?, data));
		this.morx = tables.morx.and_then(|data| morx::Table::parse(num_glyphs?, data));
		this.prop = tables.prop.and_then(|data| prop::Table::parse(num_glyphs?, data));
		this.trak = tables.trak.and_then(trak::Table::parse);
		// OpenType Font Variations
		this.avar = tables.avar.and_then(avar::Table::parse);
		this.cvar = tables.cvar.and_then(cvar::Table::parse);
		this.fvar = tables.fvar.and_then(fvar::Table::parse);
		this.gvar = tables.gvar.and_then(gvar::Table::parse);
		this.hvar = tables.hvar.and_then(hvar::Table::parse);
		this.mvar = tables.mvar.and_then(mvar::Table::parse);
		this.vvar = tables.vvar.and_then(vvar::Table::parse);
		// Other
		this.fftm = tables.fftm.and_then(fftm::Table::parse);
		Some(this)
	}
}

/// A trait for glyph outline construction.
pub trait OutlineBuilder {
	/// Appends a MoveTo segment.
	///
	/// Start of a contour.
	fn move_to(&mut self, x: f32, y: f32);

	/// Appends a LineTo segment.
	fn line_to(&mut self, x: f32, y: f32);

	/// Appends a QuadTo segment.
	fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32);

	/// Appends a CurveTo segment.
	fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32);

	/// Appends a ClosePath segment.
	///
	/// End of a contour.
	fn close(&mut self);
}

struct DummyOutline;
impl OutlineBuilder for DummyOutline {
	fn move_to(&mut self, _: f32, _: f32) {}
	fn line_to(&mut self, _: f32, _: f32) {}
	fn quad_to(&mut self, _: f32, _: f32, _: f32, _: f32) {}
	fn curve_to(&mut self, _: f32, _: f32, _: f32, _: f32, _: f32, _: f32) {}
	fn close(&mut self) {}
}

/// A glyph's raster image.
///
/// Note, that glyph metrics are in pixels and not in font units.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Raster<'a> {
	/// Horizontal offset.
	pub x: i16,

	/// Vertical offset.
	pub y: i16,

	/// Image width.
	///
	/// It doesn't guarantee that this value is the same as set in the `data`.
	pub width: u16,

	/// Image height.
	///
	/// It doesn't guarantee that this value is the same as set in the `data`.
	pub height: u16,

	/// A pixels per em of the selected strike.
	pub pixels_per_em: u16,

	/// An image format.
	pub format: RasterFormat,

	/// A raw image data. It's up to the caller to decode it.
	pub data: &'a [u8],
}

/// A glyph raster image format.
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum RasterFormat {
	PNG,

	/// A monochrome bitmap.
	///
	/// The most significant bit of the first byte corresponds to the top-left pixel, proceeding
	/// through succeeding bits moving left to right. The data for each row is padded to a byte
	/// boundary, so the next row begins with the most significant bit of a new byte. 1 corresponds
	/// to black, and 0 to white.
	BitmapMono,

	/// A packed monochrome bitmap.
	///
	/// The most significant bit of the first byte corresponds to the top-left pixel, proceeding
	/// through succeeding bits moving left to right. Data is tightly packed with no padding. 1
	/// corresponds to black, and 0 to white.
	BitmapMonoPacked,

	/// A grayscale bitmap with 2 bits per pixel.
	///
	/// The most significant bits of the first byte corresponds to the top-left pixel, proceeding
	/// through succeeding bits moving left to right. The data for each row is padded to a byte
	/// boundary, so the next row begins with the most significant bit of a new byte.
	BitmapGray2,

	/// A packed grayscale bitmap with 2 bits per pixel.
	///
	/// The most significant bits of the first byte corresponds to the top-left pixel, proceeding
	/// through succeeding bits moving left to right. Data is tightly packed with no padding.
	BitmapGray2Packed,

	/// A grayscale bitmap with 4 bits per pixel.
	///
	/// The most significant bits of the first byte corresponds to the top-left pixel, proceeding
	/// through succeeding bits moving left to right. The data for each row is padded to a byte
	/// boundary, so the next row begins with the most significant bit of a new byte.
	BitmapGray4,

	/// A packed grayscale bitmap with 4 bits per pixel.
	///
	/// The most significant bits of the first byte corresponds to the top-left pixel, proceeding
	/// through succeeding bits moving left to right. Data is tightly packed with no padding.
	BitmapGray4Packed,

	/// A grayscale bitmap with 8 bits per pixel.
	///
	/// The first byte corresponds to the top-left pixel, proceeding through succeeding bytes
	/// moving left to right.
	BitmapGray8,

	/// A color bitmap with 32 bits per pixel.
	///
	/// The first group of four bytes corresponds to the top-left pixel, proceeding through
	/// succeeding pixels moving left to right. Each byte corresponds to a color channel and the
	/// channels within a pixel are in blue, green, red, alpha order. Color values are
	/// pre-multiplied by the alpha. For example, the color "full-green with half translucency"
	/// is encoded as `\x00\x80\x00\x80`, and not `\x00\xFF\x00\x80`.
	BitmapPremulBgra32,
}

#[cfg(test)]
mod tests {
	use std::fs::File;
	use std::io::Read;

	use super::*;

	pub fn dump_face(_face: &Face, _file: &str) {
		// use std::io::Write;

		// let cmap = face.cmap.as_ref().map(|raw| chars::Encodings::parse_ot(raw)).flatten().unwrap();
		// let name = face.name.as_ref().map(|raw| names::Names::parse_ot(raw)).flatten().unwrap();
		// let mut layout = layout::Layout::default();
		// face.gdef.as_ref().map(|v| layout.parse_gdef(v));
		// face.gsub.as_ref().map(|v| layout.parse_gsub(v));
		// face.gpos.as_ref().map(|v| layout.parse_gpos(v));

		// let mut f = std::fs::File::create(format!("target/{}.txt", file)).unwrap();
		// _ = f.write_fmt(format_args!("face:{:#?}\n\n{:#?}\n\n{:#?}\n\n{:#?}", face, layout, name, cmap));
	}

	#[test]
	fn test_read_otf() {
		let mut data = vec![];
		File::open("assets/anodina.otf").unwrap().read_to_end(&mut data).unwrap();
		for index in 0..guess_format(&data).unwrap().1 {
			dump_face(&parse_face(&data, index).unwrap(), "andodina");
		}
	}
	#[test]
	fn test_read_ttf() {
		let mut data = vec![];
		File::open("assets/anodina.ttf").unwrap().read_to_end(&mut data).unwrap();
		for index in 0..guess_format(&data).unwrap().1 {
			dump_face(&parse_face(&data, index).unwrap(), "anodina");
		}
	}
	#[test]
	fn test_read_roboto() {
		let mut data = vec![];
		File::open("assets/Roboto.ttf").unwrap().read_to_end(&mut data).unwrap();
		for index in 0..guess_format(&data).unwrap().1 {
			dump_face(&parse_face(&data, index).unwrap(), "Roboto");
		}
	}
	#[test]
	fn test_read_otc_courier() {
		let mut data = vec![];
		File::open("assets/Courier.ttc").unwrap().read_to_end(&mut data).unwrap();
		dump_face(&parse_face(&data, 0).unwrap(), "Courier");
	}
	#[test]
	fn test_read_otc_times() {
		let mut data = vec![];
		File::open("assets/Times.ttc").unwrap().read_to_end(&mut data).unwrap();
		dump_face(&parse_face(&data, 0).unwrap(), "Times");
	}
	#[test]
	fn test_read_otc_source() {
		let mut data = vec![];
		File::open("assets/Source.ttc").unwrap().read_to_end(&mut data).unwrap();
		let _face = parse_face(&data, 0).unwrap();
		// dump_face(&_face, "Source");
	}
	#[test]
	fn test_read_otc_stheiti() {
		let mut data = vec![];
		File::open("assets/STHeiti.ttc").unwrap().read_to_end(&mut data).unwrap();
		let _face = parse_face(&data, 0).unwrap();
		// dump_face(&_face, "STHeiti");
	}
	#[test]
	fn test_read_otc_pingfang() {
		let mut data = vec![];
		File::open("assets/PingFangUI.ttc").unwrap().read_to_end(&mut data).unwrap();
		// for index in 0..guess_format(&data).map(|v| v.1).unwrap_or(1) {
		// 	parse_face(&data, index);
		// }
		let _face = parse_face(&data, 0).unwrap();
		// dump_face(&_face, "PingFangUI");
	}
}
